import { Fiber } from '../types.js';
import { getDisplayName } from '../core.js';
import { getOwnerStack } from './owner-stack.js';
import { getSourceFromSourceMap, getSourceMap } from './symbolication.js';
import { StackFrame } from './parse-stack.js';

const extractComponentNameFromSource = (
  sourceContent: string,
  lineNumber: number,
): string | null => {
  const lines = sourceContent.split('\n');
  const targetLineIndex = lineNumber - 1;

  if (targetLineIndex < 0 || targetLineIndex >= lines.length) {
    return null;
  }

  const startLine = Math.max(0, targetLineIndex - 5);
  const endLine = Math.min(lines.length, targetLineIndex + 5);
  const contextLines = lines.slice(startLine, endLine).join('\n');

  const arrowFunctionPattern = /(?:^|export\s+)(?:const|let|var)\s+(\w+)\s*=/m;
  const functionPattern = /(?:^|export\s+)function\s+(\w+)/m;
  const classPattern = /(?:^|export\s+)class\s+(\w+)/m;

  const arrowMatch = contextLines.match(arrowFunctionPattern);
  if (arrowMatch?.[1]) {
    return arrowMatch[1];
  }

  const functionMatch = contextLines.match(functionPattern);
  if (functionMatch?.[1]) {
    return functionMatch[1];
  }

  const classMatch = contextLines.match(classPattern);
  if (classMatch?.[1]) {
    return classMatch[1];
  }

  return null;
};

export const getDisplayNameFromSource = async (
  fiber: Fiber,
  cache = true,
  fetchFn?: (url: string) => Promise<Response>,
): Promise<string | null> => {
  const ownerStack = await getOwnerStack(fiber, cache, fetchFn);
  const stackFrame = ownerStack.filter((stackFrame) => stackFrame.fileName)[0];

  if (!stackFrame?.fileName) {
    return getDisplayName(fiber.type);
  }

  const bundleSourceMap = await getSourceMap(
    stackFrame.fileName,
    cache,
    fetchFn,
  );

  if (!bundleSourceMap) {
    return getDisplayName(fiber.type);
  }

  let source: StackFrame | null = null;

  if (
    typeof stackFrame.lineNumber === 'number' &&
    typeof stackFrame.columnNumber === 'number'
  ) {
    source = getSourceFromSourceMap(
      bundleSourceMap,
      stackFrame.lineNumber,
      stackFrame.columnNumber,
    );
  }

  if (!source?.fileName || !source.lineNumber) {
    return getDisplayName(fiber.type);
  }

  if (!bundleSourceMap.sourcesContent) {
    return getDisplayName(fiber.type);
  }

  const sourceIndex = bundleSourceMap.sources.indexOf(source.fileName);
  if (sourceIndex === -1 || !bundleSourceMap.sourcesContent[sourceIndex]) {
    return getDisplayName(fiber.type);
  }

  const sourceContent = bundleSourceMap.sourcesContent[sourceIndex];
  const extractedName = extractComponentNameFromSource(
    sourceContent,
    source.lineNumber,
  );

  if (extractedName) {
    return extractedName;
  }

  return getDisplayName(fiber.type);
};
